home *** CD-ROM | disk | FTP | other *** search
/ AI Game Programming Wisdom / AIGameProgrammingWisdom.iso / SourceCode / 11 Learning / 08 Manslow / CConditionalDistribution.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  2001-10-02  |  16.3 KB  |  585 lines

  1. //Tanks
  2. //Copyright John Manslow
  3. //29/09/2001
  4.  
  5. ////////////////////////////////////////////////////
  6. //Remove this include if not compiling under windows
  7. #include "stdafx.h"
  8. #define new DEBUG_NEW
  9. #define false FALSE
  10. ////////////////////////////////////////////////////
  11. #include "CConditionalDistribution.h"
  12. #include "stdlib.h"
  13. #include "math.h"
  14. #include "stdio.h"
  15. #include "assert.h"
  16. #include "fstream.h"
  17. #include "time.h"
  18.  
  19. //Where backups will be made during training (guards against crashes, fires, etc.)
  20. #define FileForBackupSaves "ErrorTrainingBackup.cdm"
  21.  
  22. CConditionalDistribution::CConditionalDistribution(
  23.             const unsigned long ulNewNumberOfInputs,
  24.             const unsigned long ulNewNumberOfHiddenNodes,
  25.             const unsigned long ulNewNumberOfOutputs
  26.             )
  27. {
  28.     TRACE("\t\tCreating conditional distribution...");
  29.  
  30.     //Record the structure (number of inputs, hidden neurons, and bins) of the model
  31.     ulNumberOfInputs=ulNewNumberOfInputs;
  32.     ulNumberOfHiddenNodes=ulNewNumberOfHiddenNodes;
  33.     ulNumberOfOutputs=ulNewNumberOfOutputs;
  34.  
  35.     //Allocate memory to store the model's structure
  36.     AllocateMemory();
  37.  
  38.     //Reset the learning procedure
  39.     Reset();
  40.  
  41.     //Set these character pointers to NULL so we know they're not used yet
  42.     pTrainingStartTime=NULL;
  43.     pTrainingStartDate=NULL;
  44.  
  45.     TRACE("successful.\n");
  46. }
  47.  
  48. void CConditionalDistribution::AllocateMemory(void)
  49. {
  50.     unsigned long i;
  51.  
  52.     //Allocate memory to store the current values of the input to hidden layer weights 
  53.     //of the neural network component of the model and their best values
  54.     ppdwih=new double*[ulNumberOfHiddenNodes];
  55.     ppdBestwih=new double*[ulNumberOfHiddenNodes];
  56.     assert(ppdwih && ppdBestwih);
  57.     for(i=0;i<ulNumberOfHiddenNodes;i++)
  58.     {
  59.         ppdwih[i]=new double[ulNumberOfInputs+1];
  60.         ppdBestwih[i]=new double[ulNumberOfInputs+1];
  61.         assert(ppdwih[i] && ppdBestwih[i]);
  62.     }
  63.  
  64.     //Do the same for the hidden to output layer weights
  65.     ppdwho=new double*[ulNumberOfOutputs];
  66.     ppdBestwho=new double*[ulNumberOfOutputs];
  67.     assert(ppdwho && ppdBestwho);
  68.     for(i=0;i<ulNumberOfOutputs;i++)
  69.     {
  70.         ppdwho[i]=new double[ulNumberOfHiddenNodes+1];
  71.         ppdBestwho[i]=new double[ulNumberOfHiddenNodes+1];
  72.         assert(ppdwho[i] && ppdBestwho[i]);
  73.     }
  74. }
  75.  
  76. void CConditionalDistribution::DeallocateMemory(void)
  77. {
  78.     //Deallocate the storage used for current and best weight values.
  79.     unsigned long i;
  80.     for(i=0;i<ulNumberOfHiddenNodes;i++)
  81.     {
  82.         delete []ppdwih[i];
  83.         delete []ppdBestwih[i];
  84.     }
  85.     delete []ppdwih;
  86.     delete []ppdBestwih;
  87.     for(i=0;i<ulNumberOfOutputs;i++)
  88.     {
  89.         delete []ppdwho[i];
  90.         delete []ppdBestwho[i];
  91.     }
  92.     delete []ppdwho;
  93.     delete []ppdBestwho;
  94.  
  95.     //If we've recorded the time and date of the start of training, delete them too.
  96.     if(pTrainingStartTime)
  97.     {
  98.         delete []pTrainingStartTime;
  99.     }
  100.     if(pTrainingStartDate)
  101.     {
  102.         delete []pTrainingStartDate;
  103.     }
  104. }
  105.  
  106. CConditionalDistribution::~CConditionalDistribution()
  107. {
  108.     TRACE("\t\tDestroying conditional distribution...");
  109.     DeallocateMemory();
  110.     TRACE("successful.\n");
  111. }
  112.  
  113. void CConditionalDistribution::Reset(void)
  114. {
  115.     unsigned long i,j;
  116.  
  117.     //Give the neural network component of the model weights random values between -1 and +1.
  118.     //Since this effectively resets training the best recorded weights (stored in ppdBest...) are set 
  119.     //to the new random values.
  120.     for(i=0;i<ulNumberOfHiddenNodes;i++)
  121.     {
  122.         for(j=0;j<ulNumberOfInputs+1;j++)
  123.         {
  124.             ppdwih[i][j]=2.0*(double(rand())/double(RAND_MAX)-0.5);
  125.             ppdBestwih[i][j]=ppdwih[i][j];
  126.         }
  127.     }
  128.  
  129.     //Do the same for the hidden to output layer weights
  130.     for(i=0;i<ulNumberOfOutputs;i++)
  131.     {
  132.         for(j=0;j<ulNumberOfHiddenNodes+1;j++)
  133.         {
  134.             ppdwho[i][j]=2.0*(double(rand())/double(RAND_MAX)-0.5);
  135.             ppdBestwho[i][j]=ppdwho[i][j];
  136.         }
  137.     }
  138.  
  139.     //Reset the best recorded error to an impossible value (error is always positive) to indicate
  140.     //that no training has taken place with the current values of the weights.
  141.     dBestError=-1.0;
  142.  
  143.     //Reset the step size to a conservative value.
  144.     dStepSize=0.001;
  145. }
  146.  
  147. //This function trains the conditional distribution model by using a perturbation search (as discussed in the
  148. //book Game Programming Gems 2) to search for weights its neural network component 
  149. //that are most consistent with the exemplar data passed to this function 
  150. double CConditionalDistribution::dTrainingStep(
  151.                             const unsigned long ulNumberOfPatternsInTrainingSet,
  152.                             double ** const ppdTrainingInputs,
  153.                             double * const ppdTrainingTargets
  154.                             )
  155. {
  156.     unsigned long i,j;
  157.     double dNewError;
  158.  
  159.     //If dBestError=-1, this is the first training step so we need to do some initialisation
  160.     if(dBestError==-1.0)
  161.     {
  162.         //Make sure we deallocate memory pointed to by these pointers (if any) before we 
  163.         //reassign them
  164.         if(pTrainingStartTime)
  165.         {
  166.             delete []pTrainingStartTime;
  167.         }
  168.         if(pTrainingStartDate)
  169.         {
  170.             delete []pTrainingStartDate;
  171.         }
  172.  
  173.         //Record time and date that training started.
  174.         pTrainingStartTime=new char[256];
  175.         _strtime(pTrainingStartTime);
  176.         pTrainingStartDate=new char[256];
  177.         _strdate(pTrainingStartDate);
  178.  
  179.         //Measure the performance of the model with the weights set to their current values.
  180.         //Since this is the only performance measurement so far, it must be the best. Store it.
  181.         dBestError=dGetPerformance(
  182.                                     ulNumberOfPatternsInTrainingSet,
  183.                                     ppdTrainingInputs,
  184.                                     ppdTrainingTargets
  185.                                     );
  186.     }
  187.  
  188.     //Perturb the model's weights by adding a random value between +dStepSize and -StepSize
  189.     //to each one.
  190.     for(i=0;i<ulNumberOfHiddenNodes;i++)
  191.     {
  192.         for(j=0;j<ulNumberOfInputs+1;j++)
  193.         {
  194.             ppdwih[i][j]+=dStepSize*(double(rand())/double(RAND_MAX)-0.5)*2.0;
  195.         }
  196.     }
  197.  
  198.     //And for the hidden to output layer weights
  199.     for(i=0;i<ulNumberOfOutputs;i++)
  200.     {
  201.         for(j=0;j<ulNumberOfHiddenNodes+1;j++)
  202.         {
  203.             ppdwho[i][j]+=dStepSize*(double(rand())/double(RAND_MAX)-0.5)*2.0;
  204.         }
  205.     }
  206.  
  207.     //Measure the performance of the model with its new weights
  208.     dNewError=dGetPerformance(
  209.                                 ulNumberOfPatternsInTrainingSet,
  210.                                 ppdTrainingInputs,
  211.                                 ppdTrainingTargets
  212.                                 );
  213.  
  214.     //If its performance has deteriorated (the new error is larger)
  215.     if(dNewError>dBestError)
  216.     {
  217.         //Reduce the size of the perturbation a bit - we need to be more conservative!
  218.         dStepSize*=0.9;
  219.  
  220.         //Restore the weights back to their old values
  221.         for(i=0;i<ulNumberOfHiddenNodes;i++)
  222.         {
  223.             for(j=0;j<ulNumberOfInputs+1;j++)
  224.             {
  225.                 ppdwih[i][j]=ppdBestwih[i][j];
  226.             }
  227.         }
  228.         for(i=0;i<ulNumberOfOutputs;i++)
  229.         {
  230.             for(j=0;j<ulNumberOfHiddenNodes+1;j++)
  231.             {
  232.                 ppdwho[i][j]=ppdBestwho[i][j];
  233.             }
  234.         }
  235.     }
  236.     else
  237.     {
  238.         //Otherwise the new weights performed at least as well as the old ones, so record the
  239.         //performance of the model with its new weights,
  240.         dBestError=dNewError;
  241.  
  242.         //Increase the step size a little - we're doing well and can afford to be more 
  243.         //adventurous.
  244.         dStepSize*=1.2;
  245.  
  246.         //Record the new weights as the best so far discovered
  247.         for(i=0;i<ulNumberOfHiddenNodes;i++)
  248.         {
  249.             for(j=0;j<ulNumberOfInputs+1;j++)
  250.             {
  251.                 ppdBestwih[i][j]=ppdwih[i][j];
  252.             }
  253.         }
  254.         for(i=0;i<ulNumberOfOutputs;i++)
  255.         {
  256.             for(j=0;j<ulNumberOfHiddenNodes+1;j++)
  257.             {
  258.                 ppdBestwho[i][j]=ppdwho[i][j];
  259.             }
  260.         }
  261.  
  262.         //Save the model just in case we have a crash (or power failure or something)
  263.         Save(FileForBackupSaves);
  264.     }
  265.  
  266.     //Tell the calling function what the performance of the model currently is, so it can 
  267.     //decide whether to continue training. This function always leaves the model with the 
  268.     //best weights found so far, so there's no need to restore them externally at the end of 
  269.     //training
  270.     return dBestError;
  271. }
  272.  
  273. //This function uses the neural network component of the conditional distribution model to calculate
  274. //the probability associated with each bin in the distribution. This is done simply by querying the neural
  275. //network with the distribution inputs because it has one output for each bin
  276. double *CConditionalDistribution::pdGetBinProbabilities(
  277.                             const double * const pdInputs
  278.                             )
  279. {
  280.     //For detailed comments on querying a neural network, see CMLP.cpp
  281.     unsigned long i,j;
  282.  
  283.     //Declare storage for the activities of the hidden and output neurons
  284.     double *pdah=new double[ulNumberOfHiddenNodes];
  285.     double *pdao=new double[ulNumberOfOutputs];
  286.  
  287.     //Declare storage for the amount of stimulation coming onto a neuron
  288.     double dStimulus;
  289.  
  290.     //Calculate the activity of the network's hidden neurons.
  291.     for(i=0;i<ulNumberOfHiddenNodes;i++)
  292.     {
  293.         dStimulus=ppdwih[i][0];
  294.         for(j=1;j<ulNumberOfInputs+1;j++)
  295.         {
  296.             dStimulus+=ppdwih[i][j]*pdInputs[j-1];
  297.         }
  298.         pdah[i]=1.0/(1.0+exp(-dStimulus));
  299.     }
  300.     double dTotalOutput=0.0;
  301.  
  302.     //Unlike the neural network in CMLP.cpp, the one used here has a special function to calculate the
  303.     //activity of its outputs in order to guarantee that they sum to unity (as befits closed-world probabilities). 
  304.     //This function is called "softmax" and is described more fully in books such as [Bishop95]
  305.     for(i=0;i<ulNumberOfOutputs;i++)
  306.     {
  307.         dStimulus=ppdwho[i][0];
  308.         for(j=1;j<ulNumberOfHiddenNodes+1;j++)
  309.         {
  310.             dStimulus+=ppdwho[i][j]*pdah[j-1];
  311.         }
  312.  
  313.         //Translate this stimulation into the activity of the output neuron using the activation 
  314.         //function:
  315.         pdao[i]=exp(dStimulus);
  316.         dTotalOutput+=pdao[i];
  317.     }
  318.  
  319.     //Deallocate the temporary storage that was used for the hidden neuron activities
  320.     delete []pdah;
  321.  
  322.     //Normalise the output neuron activities
  323.     for(i=0;i<ulNumberOfOutputs;i++)
  324.     {
  325.         pdao[i]/=dTotalOutput;
  326.     }
  327.  
  328.     //Remember that we're returning a pointer to "new"ed storage, so the calling function
  329.     //must deallocate it to avoid memory leaks.
  330.     return pdao;
  331. }
  332.  
  333. //Generates a sample from the conditional distribution by selecting one of the distribution's bins in 
  334. //accordance with their probabilities and generating a specific sample from within the bin
  335. double CConditionalDistribution::dGetOutputs(
  336.                             const double * const pdInputs
  337.                             )
  338. {
  339.     //Use the neural network to calculate the bin probabilities as a function of the conditional distribution's inputs
  340.     double *pdOutputs;
  341.     pdOutputs=pdGetBinProbabilities(pdInputs);
  342.  
  343.     unsigned long ulCurrentBin;
  344.     double dOutput;
  345.     //This outer loop guarantees that events less likely than 1 in 100 do not occur. Though not strictly necessary
  346.     //it helps to avoid samples that cannot be justified statistically on the basis of the model's training data.
  347.     do
  348.     {
  349.         //The sampling mechanism in this inner loop is identical to that of the unconditional distribution
  350.         ulCurrentBin=0;
  351.         double dTargetProbability=double(rand())/double(RAND_MAX);
  352.         double dAccumulator=pdOutputs[ulCurrentBin];
  353.         while(dTargetProbability>dAccumulator)
  354.         {
  355.             ulCurrentBin++;
  356.             dAccumulator+=pdOutputs[ulCurrentBin];
  357.         }
  358.     }
  359.     while(pdOutputs[ulCurrentBin]<0.01);
  360.  
  361.     dOutput=double(ulCurrentBin)/double(ulNumberOfOutputs)+double(rand())/double(RAND_MAX)*(1.0/double(ulNumberOfOutputs));
  362.  
  363.     delete []pdOutputs;
  364.  
  365.     return dOutput;
  366. }
  367.  
  368. double CConditionalDistribution::dGetPerformance(void)
  369. {
  370.     //dBestError is computed by a call to dGetPerformance
  371.     return dBestError;
  372. }
  373.  
  374. //This function measures the performance of the conditional distribution model - that is how consistent it is with 
  375. //the exemplar data passed to this function. This consistency is measured as the mean log-probability with which 
  376. //the distribution model "explains" the data.
  377. double CConditionalDistribution::dGetPerformance(
  378.                                 const unsigned long ulNumberOfPatterns,
  379.                                 double ** const ppdInputs,
  380.                                 double * const pdTargets
  381.                                 )
  382. {
  383.     unsigned long i,j;
  384.  
  385.     //Storage for the error measure
  386.     double dError=0.0;
  387.  
  388.     //Will store the neural network outputs (i.e. bin probabilities)
  389.     double *pdOutputs;
  390.  
  391.     //Prepare some storage for the binary target vector (explained below)
  392.     double *pdBinaryTargets=new double[ulNumberOfOutputs];
  393.     
  394.     //Go through each pattern (example) in the data set and
  395.     for(i=0;i<ulNumberOfPatterns;i++)
  396.     {
  397.         //Compute the probabilities associated with each bin in the distribution for the current input
  398.         pdOutputs=pdGetBinProbabilities(ppdInputs[i]);
  399.  
  400.         //Now, for each bin
  401.         for(j=0;j<ulNumberOfOutputs;j++)
  402.         {
  403.             //This section of code translates the target output associated with the current example into a 
  404.             //vector of length ulNumberOfOutputs, containing zeros in all positions but for a one corresponding to
  405.             //the bin that actually contains target output specified in the current example. 
  406.  
  407.             //This vector can be thought of as a vector of target probabiltiies for the bins in the distribution model
  408.             //and hence for the outputs of teh neural network: if the distribution predicted the examples perfectly 
  409.             //then, for this pattern, all bins would have probability zero except for the one that actually contained
  410.             //the example output.
  411.             if(pdTargets[i]>=double(j)/double(ulNumberOfOutputs) 
  412.                 && pdTargets[i]<double(j+1)/double(ulNumberOfOutputs))
  413.             {
  414.                 pdBinaryTargets[j]=1.0;
  415.             }
  416.             else
  417.             {
  418.                 pdBinaryTargets[j]=0.0;
  419.             }
  420.  
  421.             //The actual error measure is derived below. This is the cross entropy function and can only be used
  422.             //when the bin probabilities are constrained to sum to, at most, unity. (See [Bishop95].)
  423.             dError-=pdBinaryTargets[j]*log(pdOutputs[j]);
  424.         }
  425.         //Deallocate the memory used to store the network outputs.
  426.         delete []pdOutputs;
  427.     }
  428.  
  429.     //Divide the error by the number of patterns to give the average error per sample - makes
  430.     //the error measure independent of the number of samples and hence a little more 
  431.     //interpretable.
  432.     dError/=double(ulNumberOfPatterns);
  433.  
  434.     //Clean up allocated memory
  435.     delete []pdBinaryTargets;
  436.  
  437.     //Return the computed error
  438.     return dError;
  439. }
  440.  
  441. int CConditionalDistribution::Save(const char * const pFileName)
  442. {
  443.     unsigned long i,j;
  444.     assert(pFileName);
  445.  
  446.     //Create the output stream to save the network to.
  447.     ofstream *pOut=new ofstream(pFileName);
  448.  
  449.     //Make sure it was created and opened successfully.
  450.     if(!pOut)
  451.     {
  452.         assert(false);
  453.         return 0;
  454.     }
  455.     if(!pOut->is_open())
  456.     {
  457.         assert(false);
  458.         delete pOut;
  459.         return 0;
  460.     }
  461.     //Make sure we don't lose information.
  462.     pOut->precision(15);
  463.  
  464.     //Save all the network info:
  465.     //Its structure...
  466.     *pOut<<ulNumberOfInputs;
  467.     *pOut<<"\n";
  468.     *pOut<<ulNumberOfHiddenNodes;
  469.     *pOut<<"\n";
  470.     *pOut<<ulNumberOfOutputs;
  471.     *pOut<<"\n";
  472.  
  473.     //Its weights
  474.     for(i=0;i<ulNumberOfHiddenNodes;i++)
  475.     {
  476.         for(j=0;j<ulNumberOfInputs+1;j++)
  477.         {
  478.             *pOut<<ppdwih[i][j];
  479.             *pOut<<"\t";
  480.         }
  481.         *pOut<<"\n";
  482.     }
  483.     for(i=0;i<ulNumberOfOutputs;i++)
  484.     {
  485.         for(j=0;j<ulNumberOfHiddenNodes+1;j++)
  486.         {
  487.             *pOut<<ppdwho[i][j];
  488.             *pOut<<"\t";
  489.         }
  490.         *pOut<<"\n";
  491.     }
  492.  
  493.     //When training started...
  494.     *pOut<<"Training started\t";
  495.     *pOut<<pTrainingStartTime;
  496.     *pOut<<"\t";
  497.     *pOut<<pTrainingStartDate;
  498.     *pOut<<"\n";
  499.  
  500.     //the current date and time...
  501.     char *pTime=new char[256];
  502.     *pOut<<"Current time\t\t";
  503.     _strtime(pTime);
  504.     *pOut<<pTime;
  505.     *pOut<<"\t";
  506.     _strdate(pTime);
  507.     *pOut<<pTime;
  508.     *pOut<<"\n";
  509.     delete []pTime;
  510.  
  511.     //And how well the network currently performs.
  512.     *pOut<<"Performance\t\t";
  513.     *pOut<<dBestError;
  514.     *pOut<<"\n";
  515.  
  516.     //Close the file and delete the stream.
  517.     pOut->close();
  518.     delete pOut;
  519.  
  520.     //Return that the save was successful.
  521.     return 1;
  522. }
  523.  
  524. int CConditionalDistribution::Load(const char * const pFileName)
  525. {
  526.     unsigned long i,j;
  527.     assert(pFileName);
  528.  
  529.     //Create a stream to load the network from
  530.     ifstream *pIn=new ifstream(pFileName,ios::nocreate);
  531.  
  532.     //Check to make sure that it was created and could be opened.
  533.     if(!pIn)
  534.     {
  535.         assert(false);
  536.         return 0;
  537.     }
  538.     if(!pIn->is_open())
  539.     {
  540.         assert(false);
  541.         delete pIn;
  542.         return 0;
  543.     }
  544.  
  545.     //Since we're about to load a new network, we should delete the storage used by the
  546.     //current one to prevent memory leaks.
  547.     DeallocateMemory();
  548.  
  549.     //Load the structure of the new network
  550.     *pIn>>ulNumberOfInputs;
  551.     *pIn>>ulNumberOfHiddenNodes;
  552.     *pIn>>ulNumberOfOutputs;
  553.  
  554.     //Allocate memory to store its weights
  555.     AllocateMemory();
  556.  
  557.     //Reset its status so that it can be trained if necessary
  558.     Reset();
  559.  
  560.     //Load in its weights
  561.     for(i=0;i<ulNumberOfHiddenNodes;i++)
  562.     {
  563.         for(j=0;j<ulNumberOfInputs+1;j++)
  564.         {
  565.             *pIn>>ppdwih[i][j];
  566.             ppdBestwih[i][j]=ppdwih[i][j];
  567.         }
  568.     }
  569.     for(i=0;i<ulNumberOfOutputs;i++)
  570.     {
  571.         for(j=0;j<ulNumberOfHiddenNodes+1;j++)
  572.         {
  573.             *pIn>>ppdwho[i][j];
  574.             ppdBestwho[i][j]=ppdwho[i][j];
  575.         }
  576.     }
  577.  
  578.     //Close and delete the stream.
  579.     pIn->close();
  580.     delete pIn;
  581.  
  582.     //Indicate that we've been successful.
  583.     return 1;
  584. }
  585.